package com.carey.blog.repository.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONException;
import org.json.JSONObject;

import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.Latkes;
import com.carey.blog.framework.latke.RunsOnEnv;
import com.carey.blog.framework.latke.cache.Cache;
import com.carey.blog.framework.latke.cache.CacheFactory;
import com.carey.blog.framework.latke.repository.RepositoryException;
import com.carey.blog.framework.latke.repository.gae.AbstractGAERepository;
import com.carey.blog.model.Article;
import com.carey.blog.model.Comment;
import com.carey.blog.repository.ArticleCommentRepository;
import com.carey.blog.repository.ArticleRepository;
import com.carey.blog.repository.CommentRepository;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.QueryResultIterable;

/**
 * Comment Google App Engine repository.
 * 
 */
public final class CommentGAERepository extends AbstractGAERepository implements
		CommentRepository {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(CommentGAERepository.class.getName());
	/**
	 * Cache.
	 */
	private static final Cache<String, Object> CACHE;
	/**
	 * Key of the recent comments cache count.
	 */
	private static final String KEY_RECENT_COMMENTS_CACHE_CNT = "mostRecentCommentsCacheCnt";
	/**
	 * Article-Comment repository.
	 */
	private ArticleCommentRepository articleCommentRepository = ArticleCommentGAERepository
			.getInstance();
	/**
	 * Article repository.
	 */
	private ArticleRepository articleRepository = ArticleGAERepository
			.getInstance();

	static {
		final RunsOnEnv runsOnEnv = Latkes.getRunsOnEnv();
		if (!runsOnEnv.equals(RunsOnEnv.GAE)) {
			throw new RuntimeException(
					"GAE repository can only runs on Google App Engine, please "
							+ "check your configuration and make sure "
							+ "Latkes.setRunsOnEnv(RunsOnEnv.GAE) was invoked before "
							+ "using GAE repository.");
		}

		CACHE = CacheFactory.getCache("CommentGAERepositoryCache");
	}

	@Override
	public String getName() {
		return Comment.COMMENT;
	}

	@Override
	public List<JSONObject> getRecentComments(final int num)
			throws RepositoryException {
		final String cacheKey = KEY_RECENT_COMMENTS_CACHE_CNT + "[" + num + "]";
		@SuppressWarnings("unchecked")
		List<JSONObject> ret = (List<JSONObject>) CACHE.get(cacheKey);
		if (null != ret) {
			LOGGER.log(Level.FINEST, "Got the recent comments from cache");
		} else {
			ret = new ArrayList<JSONObject>();
			final Query query = new Query(getName());
			query.addSort(Keys.OBJECT_ID, Query.SortDirection.DESCENDING);
			final PreparedQuery preparedQuery = getDatastoreService().prepare(
					query);
			final QueryResultIterable<Entity> queryResultIterable = preparedQuery
					.asQueryResultIterable(FetchOptions.Builder.withLimit(num));

			for (final Entity entity : queryResultIterable) {
				final JSONObject comment = entity2JSONObject(entity);
				ret.add(comment);
			}

			try {
				removeForUnpublishedArticles(ret);
			} catch (final JSONException e) {
				LOGGER.log(Level.SEVERE, e.getMessage(), e);
			}

			CACHE.put(cacheKey, ret);

			LOGGER.log(Level.FINEST,
					"Got the recent comments, then put it into cache");
		}

		return ret;
	}

	/**
	 * Removes comments of unpublished articles for the specified comments.
	 * 
	 * @param comments
	 *            the specified comments
	 * @throws JSONException
	 *             json exception
	 * @throws RepositoryException
	 *             repository exception
	 */
	private void removeForUnpublishedArticles(final List<JSONObject> comments)
			throws JSONException, RepositoryException {
		LOGGER.finer("Removing unpublished articles' comments....");
		final Iterator<JSONObject> iterator = comments.iterator();
		while (iterator.hasNext()) {
			final JSONObject comment = iterator.next();
			final String commentId = comment.getString(Keys.OBJECT_ID);
			final JSONObject articleCommentRelation = articleCommentRepository
					.getByCommentId(commentId);
			if (null == articleCommentRelation) {
				continue; // This comment is a page comment or comment has been
							// removed just
			}
			final String articleId = articleCommentRelation
					.getString(Article.ARTICLE + "_" + Keys.OBJECT_ID);
			if (!articleRepository.isPublished(articleId)) {
				iterator.remove();
			}
		}

		LOGGER.finer("Removed unpublished articles' comments....");
	}

	/**
	 * Gets the {@link CommentGAERepository} singleton.
	 * 
	 * @return the singleton
	 */
	public static CommentGAERepository getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private CommentGAERepository() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final CommentGAERepository SINGLETON = new CommentGAERepository();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
